package com.ejkj.pdf;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import lombok.*;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import sun.misc.BASE64Decoder;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Map;

/**
 * @author wenjun
 * @description
 * @created 2020/9/7
 */


@Builder(toBuilder = true)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Pdf {

	//pdf模板
	private String template;

	// pdf填充参数
	private Map<String, String> params;

	/// 图片填充参数  图片参数为base64的字符串
	private Map<String, String> imgs;

	// 设置默认字体：宋体
	@Builder.Default
	private String font = "fonts/simsun.ttf";

	// 字体大小
	@Builder.Default
	private float fontSize = 15f;

	// 数字签名 begin
	// 缺少一个要素，不会签名
	// 签章图片
	private String signImg;

	// 证书
	private String key;

	// 证书密码
	private String password;
	// 数字签名 end

	/**
	 * 生成pdf
	 *
	 * @return
	 * @throws Exception
	 */
	public byte[] getPdfbytes() throws Exception {
		// 检查模板文件
		if (StringUtils.isEmpty(this.template)) {
			throw new RuntimeException("template is empty");
		}

		byte[] resultBytes = null;
		//数据填充
		PdfReader pdfReader = new PdfReader(template);
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		PdfStamper ps = new PdfStamper(pdfReader, bos);
		AcroFields fields = ps.getAcroFields();
		//字体
		BaseFont baseFont = getFont();

		// 检查填充参数
		if (MapUtils.isNotEmpty(this.params)) {
			// 填充字段属性
			for (String key : params.keySet()) {
				String value = params.get(key);
				if (baseFont != null) {
					fields.setFieldProperty(key, "textfont", baseFont, null);
					fields.setFieldProperty(key, "textsize", fontSize, null);
				}
				fields.setField(key, value);
			}
		}
		//图片填充
		if (MapUtils.isNotEmpty(this.imgs)) {
			for (String key : imgs.keySet()) {
				// 通过域名获取所在页和坐标，左下角为起点
				int pageNo = fields.getFieldPositions(key).get(0).page;
				Rectangle signRect = fields.getFieldPositions(key).get(0).position;
				float x = signRect.getLeft();
				float y = signRect.getBottom();

				// 读图片
				byte[] signImgBytes = new BASE64Decoder().decodeBuffer(imgs.get(key).trim());
				Image image = Image.getInstance(signImgBytes);

				// 获取操作的页面
				PdfContentByte under = ps.getOverContent(pageNo);
				// 根据域的大小缩放图片
				image.scaleToFit(signRect.getWidth(), signRect.getHeight());
				// 添加图片
				image.setAbsolutePosition(x, y);
				under.addImage(image);
			}
		}
		ps.setFormFlattening(true);
		ps.close();
		resultBytes = bos.toByteArray();

		// 具备签名要素，进行签名
		if (StringUtils.isNotEmpty(this.signImg) && StringUtils.isNotEmpty(this.key) && StringUtils.isNotEmpty(this.password)) {
			// 签名页码
			int signPage = fields.getFields().get("Signature").getPage(0);
			// 签名位置
			PdfArray signRect = (PdfArray) fields.getFields().get("Signature").getWidget(0).get(PdfName.RECT);
			long[] rect = signRect.asLongArray();
			resultBytes = sign(resultBytes, signPage, rect);
		}

		return resultBytes;
	}

	/**
	 * 获取字体
	 *
	 * @return
	 * @throws IOException
	 */
	private BaseFont getFont() throws Exception {
		return BaseFont.createFont(font, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
	}

	/**
	 * PDF 签名
	 *
	 * @param pdfBytes PDF文件
	 * @param signPage 签名页码
	 * @param rect     签名位置
	 * @return
	 * @throws Exception
	 */
	private byte[] sign(byte[] pdfBytes, int signPage, long[] rect) throws Exception {
		// 获取签名秘钥
		char[] passwd = this.password.toCharArray();
		KeyStore keyStore = KeyStore.getInstance("PKCS12");
//        keyStore.load(new ClassPathResource(this.key).getInputStream(), passwd);
		InputStream keyInput = Thread.currentThread().getContextClassLoader().getResourceAsStream(this.key);
		keyStore.load(keyInput, passwd);
		String alias = keyStore.aliases().nextElement();
		PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, passwd);
		Certificate[] chain = keyStore.getCertificateChain(alias);
		keyInput.close();

		PdfReader pdfReader = new PdfReader(pdfBytes);
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		//true的话，pdf可以被追加签名，验签工具可以识别出每次签名之后文档是否被修改
		PdfStamper stamper = PdfStamper.createSignature(pdfReader, bos, '\0', null, false);
		// 获取数字签章属性对象，设定数字签章的属性
		PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
		appearance.setReason("合同签名");
		appearance.setLocation("location");
		appearance.setVisibleSignature(new Rectangle(rect[0], rect[1], rect[2], rect[3]), signPage, "signature_real");
		//读取图章图片，这个image是itext包的image
		InputStream imgInput = Thread.currentThread().getContextClassLoader().getResourceAsStream(this.signImg);
		byte[] signImgBytes = IOUtils.toByteArray(imgInput);
		imgInput.close();
//        byte[] signImgBytes = IOUtils.toByteArray(new ClassPathResource(this.signImg).getInputStream());
		Image image = Image.getInstance(signImgBytes);
		appearance.setSignatureGraphic(image);
		appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
		//设置图章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）
		appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
		// 摘要算法
		ExternalDigest digest = new BouncyCastleDigest();
		// 签名算法
		ExternalSignature signature = new PrivateKeySignature(privateKey, DigestAlgorithms.SHA1, null);
		// 调用itext签名方法完成pdf签章
		MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, null);
		pdfReader.close();
		bos.close();
		return bos.toByteArray();
	}
}
